package sun.scheduler.core.scheduling;

import sun.scheduler.core.resource.ResourceProfileMap;
import sun.scheduler.core.resource.ResourceProfile;
import java.util.ArrayList;
import java.util.Comparator;
import sun.scheduler.core.ScheduleGraph;
import sun.scheduler.core.ScheduleGraph.StartTimes;
import sun.scheduler.core.job.DummyJob;
import sun.scheduler.core.job.Job;
import sun.scheduler.util.collections.Heap;
import sun.scheduler.util.graph.Edge;
import sun.scheduler.util.graph.Graph;
import sun.scheduler.util.graph.Node;
import sun.scheduler.util.graph.algorithm.maxflow.EdgeFlow;

/**
 * A {@link ResourceScheduler} which sets the start and end times of each {@link Job}
 * of a Schedule, such that the total amount of Resources spend is in the bounds
 * of the maximal allowed number of resources.
 *
 * @see Schedule
 * @see Job
 * @see Resource
 * @see ResourceProfile
 * @see ResourceProfileMap
 * @author mfj
 */
public class ResourceScheduler implements Runnable
{
    /**
     * The {@link CurveState} Scheduling
     */
    public final static Job.CurveState CS = Job.CurveState.Scheduling;
    /**
     * Schedule Grap which defines the process dependencies.
     */
    protected ScheduleGraph schGraph;
    /**
     * A map of all Ressource profiles.
     */
    protected ResourceProfileMap resProfiles;
    /**
     * Compares Jobs by the amount of time they would increase the project if we
     * would insert them later,
     */
    protected DIncreaseComp comp;

    /**
     *
     * @param schGraph
     * @param resProfiles
     * @param endTime
     */
    public ResourceScheduler(ScheduleGraph schGraph, ResourceProfileMap resProfiles)
    {
        this.schGraph = schGraph;
        this.resProfiles = resProfiles;
        this.comp = new DIncreaseComp(schGraph);
    }


    /**
     * Determines the starttime of the current Job using its ressource profile.
     *
     * @param Job
     */
    protected boolean schedule(Job Job)
    {
        long duration = Job.getDuration(CS);
        int cost = (int) Job.getAmountOfResources(CS);

        ResourceProfile resourceProfile = resProfiles.getResourceProfile(Job.resource);
        long nextStart = resourceProfile.next(Job.getEarliestStartTime(CS), duration, cost);
        resourceProfile.pack(nextStart, duration, cost);

        if (resourceProfile.getMinAvailableResources() < 0)
            return false;

        schGraph.increaseEarliestStartTime(Job, nextStart);
        return true;
    }

    /**
     * Performs the scheduling.
     */
    @Override
    public void run()
    {
        schGraph.setCurveState(CS);
        schGraph.updateJobTimeParameters();
        
        ArrayList<Node<StartTimes, Job>> topologicOrder = schGraph.topologicOrder();
        for (Node<StartTimes, Job> node : topologicOrder)
        {
            //consider all outgoing edges of the top. order and sort them by the
            //minimal amount of time they would increase the project.
            Heap<Job> inrcLHeap = new Heap<Job>(node.outEdges.size(), comp);
            for (Edge<StartTimes, Job> edge : node.getOutEdges())
            {
                Job curr = edge.getData();
                if (curr instanceof DummyJob)
                    continue;

                inrcLHeap.add(curr);
            }

            // schedule each Job, prioirizing those who increase the schedule by a minimal amount
            while (!inrcLHeap.isEmpty())
                schedule(inrcLHeap.poll());
        }
    }

}
